Added structured error reporting to Reader. This allows applications for interactively viewing or editing JSON to do a better job of highlighting errors. Also added offset accessors to Value, offering the same sort of functionality even for non-errors. Thanks to Zach Clifford (zacharyc@google.com) for the patch. git-svn-id: http://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk@281 1f120ed1-78a5-a849-adca-83f0a9e25bb6 
diff --git a/jsoncpp/include/json/reader.h b/jsoncpp/include/json/reader.h index 0771342..6271f71 100644 --- a/jsoncpp/include/json/reader.h +++ b/jsoncpp/include/json/reader.h 
@@ -33,6 +33,19 @@  typedef char Char;  typedef const Char *Location;   + /** \brief An error tagged with where in the JSON text it was encountered. + * + * The offsets give the [start, limit) range of bytes within the text. Note + * that this is bytes, not codepoints. + * + */ + struct StructuredError + { + size_t offset_start; + size_t offset_limit; + std::string message; + }; +  /** \brief Constructs a Reader allowing all features  * for parsing.  */ @@ -95,6 +108,14 @@  */  std::string getFormattedErrorMessages() const;   + /** \brief Returns a vector of structured erros encounted while parsing. + * \return A (possibly empty) vector of StructuredError objects. Currently + * only one error can be returned, but the caller should tolerate multiple + * errors. This can occur if the parser recovers from a non-fatal + * parse error and then encounters additional errors. + */ + std::vector<StructuredError> getStructuredErrors() const; +  private:  enum TokenType  { 
diff --git a/jsoncpp/include/json/value.h b/jsoncpp/include/json/value.h index bd7f181..f18457a 100644 --- a/jsoncpp/include/json/value.h +++ b/jsoncpp/include/json/value.h 
@@ -442,6 +442,13 @@  iterator begin();  iterator end();   + // Accessors for the [start, limit) range of bytes within the JSON text from + // which this value was parsed, if any. + void setOffsetStart( size_t start ); + void setOffsetLimit( size_t limit ); + size_t getOffsetStart() const; + size_t getOffsetLimit() const; +  private:  Value &resolveReference( const char *key,   bool isStatic ); @@ -509,6 +516,11 @@  int memberNameIsStatic_ : 1; // used by the ValueInternalMap container.  # endif  CommentInfo *comments_; + + // [start, limit) byte offsets in the source JSON text from which this Value + // was extracted. + size_t start_; + size_t limit_;  };     
diff --git a/jsoncpp/src/lib_json/json_reader.cpp b/jsoncpp/src/lib_json/json_reader.cpp index 9ba4024..4f592cd 100644 --- a/jsoncpp/src/lib_json/json_reader.cpp +++ b/jsoncpp/src/lib_json/json_reader.cpp 
@@ -215,9 +215,11 @@  {  case tokenObjectBegin:  successful = readObject( token ); + currentValue().setOffsetLimit(current_ - begin_);  break;  case tokenArrayBegin:  successful = readArray( token ); + currentValue().setOffsetLimit(current_ - begin_);  break;  case tokenNumber:  successful = decodeNumber( token ); @@ -227,12 +229,18 @@  break;  case tokenTrue:  currentValue() = true; + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_);  break;  case tokenFalse:  currentValue() = false; + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_);  break;  case tokenNull:  currentValue() = Value(); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_);  break;  case tokenArraySeparator:  if ( features_.allowDroppedNullPlaceholders_ ) @@ -241,10 +249,14 @@  // token.  current_--;  currentValue() = Value(); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_);  break;  }  // Else, fall through...  default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_);  return addError( "Syntax error: value, object or array expected.", token );  }   @@ -493,11 +505,12 @@      bool  -Reader::readObject( Token &/*tokenStart*/ ) +Reader::readObject( Token &tokenStart )  {  Token tokenName;  std::string name;  currentValue() = Value( objectValue ); + currentValue().setOffsetStart(tokenStart.start_ - begin_);  while ( readToken( tokenName ) )  {  bool initialTokenOk = true; @@ -564,9 +577,10 @@      bool  -Reader::readArray( Token &/*tokenStart*/ ) +Reader::readArray( Token &tokenStart )  {  currentValue() = Value( arrayValue ); + currentValue().setOffsetStart(tokenStart.start_ - begin_);  skipSpaces();  if ( *current_ == ']' ) // empty array  { @@ -613,6 +627,8 @@  if ( !decodeNumber( token, decoded ) )  return false;  currentValue() = decoded; + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_);  return true;  }   @@ -678,6 +694,8 @@  if ( !decodeDouble( token, decoded ) )  return false;  currentValue() = decoded; + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_);  return true;  }   @@ -729,6 +747,8 @@  if ( !decodeString( token, decoded ) )  return false;  currentValue() = decoded; + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_);  return true;  }   @@ -963,6 +983,25 @@  }     +std::vector<Reader::StructuredError> +Reader::getStructuredErrors() const +{ + std::vector<Reader::StructuredError> allErrors; + for ( Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError ) + { + const ErrorInfo &error = *itError; + Reader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +  std::istream& operator>>( std::istream &sin, Value &root )  {  Json::Reader reader; 
diff --git a/jsoncpp/src/lib_json/json_value.cpp b/jsoncpp/src/lib_json/json_value.cpp index dd8dda0..abc59c8 100644 --- a/jsoncpp/src/lib_json/json_value.cpp +++ b/jsoncpp/src/lib_json/json_value.cpp 
@@ -274,6 +274,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  switch ( type )  { @@ -318,6 +320,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.uint_ = value;  } @@ -329,6 +333,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.int_ = value;  } @@ -342,6 +348,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.int_ = value;  } @@ -354,6 +362,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.uint_ = value;  } @@ -366,6 +376,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.real_ = value;  } @@ -377,6 +389,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.string_ = duplicateStringValue( value );  } @@ -390,6 +404,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.string_ = duplicateStringValue( beginValue,   (unsigned int)(endValue - beginValue) ); @@ -403,6 +419,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.string_ = duplicateStringValue( value.c_str(),   (unsigned int)value.length() ); @@ -416,6 +434,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.string_ = const_cast<char *>( value.c_str() );  } @@ -429,6 +449,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.string_ = duplicateStringValue( value, value.length() );  } @@ -441,6 +463,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( 0 ) + , limit_( 0 )  {  value_.bool_ = value;  } @@ -453,6 +477,8 @@  , itemIsUsed_( 0 )  #endif  , comments_( 0 ) + , start_( other.start_ ) + , limit_( other.limit_ )  {  switch ( type_ )  { @@ -557,6 +583,8 @@  int temp2 = allocated_;  allocated_ = other.allocated_;  other.allocated_ = temp2; + std::swap( start_, other.start_ ); + std::swap( limit_, other.limit_ );  }    ValueType  @@ -1027,7 +1055,8 @@  Value::clear()  {  JSON_ASSERT_MESSAGE( type_ == nullValue || type_ == arrayValue || type_ == objectValue, "in Json::Value::clear(): requires complex value" ); - + start_ = 0; + limit_ = 0;  switch ( type_ )  {  #ifndef JSON_VALUE_USE_INTERNAL_MAP @@ -1556,6 +1585,34 @@  }     +void +Value::setOffsetStart( size_t start ) +{ + start_ = start; +} + + +void +Value::setOffsetLimit( size_t limit ) +{ + limit_ = limit; +} + + +size_t +Value::getOffsetStart() const +{ + return start_; +} + + +size_t +Value::getOffsetLimit() const +{ + return limit_; +} + +  std::string   Value::toStyledString() const  { 
diff --git a/jsoncpp/src/test_lib_json/main.cpp b/jsoncpp/src/test_lib_json/main.cpp index dbcbd78..f82a384 100644 --- a/jsoncpp/src/test_lib_json/main.cpp +++ b/jsoncpp/src/test_lib_json/main.cpp 
@@ -1474,6 +1474,26 @@  }     +JSONTEST_FIXTURE( ValueTest, offsetAccessors ) +{ + Json::Value x; + JSONTEST_ASSERT( x.getOffsetStart() == 0 ); + JSONTEST_ASSERT( x.getOffsetLimit() == 0 ); + x.setOffsetStart(10); + x.setOffsetLimit(20); + JSONTEST_ASSERT( x.getOffsetStart() == 10 ); + JSONTEST_ASSERT( x.getOffsetLimit() == 20 ); + Json::Value y(x); + JSONTEST_ASSERT( y.getOffsetStart() == 10 ); + JSONTEST_ASSERT( y.getOffsetLimit() == 20 ); + Json::Value z; + z.swap(y); + JSONTEST_ASSERT( z.getOffsetStart() == 10 ); + JSONTEST_ASSERT( z.getOffsetLimit() == 20 ); + JSONTEST_ASSERT( y.getOffsetStart() == 0 ); + JSONTEST_ASSERT( y.getOffsetLimit() == 0 ); +} +  struct WriterTest : JsonTest::TestCase  {  }; @@ -1490,6 +1510,115 @@  }     +struct ReaderTest : JsonTest::TestCase +{ +}; + + +JSONTEST_FIXTURE( ReaderTest, parseWithNoErrors ) +{ + Json::Reader reader; + Json::Value root; + bool ok = reader.parse( + "{ \"property\" : \"value\" }", + root); + JSONTEST_ASSERT( ok ); + JSONTEST_ASSERT( reader.getFormattedErrorMessages().size() == 0 ); + JSONTEST_ASSERT( reader.getStructuredErrors().size() == 0 ); +} + + +JSONTEST_FIXTURE( ReaderTest, parseWithNoErrorsTestingOffsets ) +{ + Json::Reader reader; + Json::Value root; + bool ok = reader.parse( + "{ \"property\" : [\"value\", \"value2\"], \"obj\" : { \"nested\" : 123, \"bool\" : true}, \"null\" : null, \"false\" : false }", + root); + JSONTEST_ASSERT( ok ); + JSONTEST_ASSERT( reader.getFormattedErrorMessages().size() == 0 ); + JSONTEST_ASSERT( reader.getStructuredErrors().size() == 0 ); + JSONTEST_ASSERT( root["property"].getOffsetStart() == 15 ); + JSONTEST_ASSERT( root["property"].getOffsetLimit() == 34 ); + JSONTEST_ASSERT( root["property"][0].getOffsetStart() == 16 ); + JSONTEST_ASSERT( root["property"][0].getOffsetLimit() == 23 ); + JSONTEST_ASSERT( root["property"][1].getOffsetStart() == 25 ); + JSONTEST_ASSERT( root["property"][1].getOffsetLimit() == 33 ); + JSONTEST_ASSERT( root["obj"].getOffsetStart() == 44 ); + JSONTEST_ASSERT( root["obj"].getOffsetLimit() == 76 ); + JSONTEST_ASSERT( root["obj"]["nested"].getOffsetStart() == 57 ); + JSONTEST_ASSERT( root["obj"]["nested"].getOffsetLimit() == 60 ); + JSONTEST_ASSERT( root["obj"]["bool"].getOffsetStart() == 71 ); + JSONTEST_ASSERT( root["obj"]["bool"].getOffsetLimit() == 75 ); + JSONTEST_ASSERT( root["null"].getOffsetStart() == 87 ); + JSONTEST_ASSERT( root["null"].getOffsetLimit() == 91 ); + JSONTEST_ASSERT( root["false"].getOffsetStart() == 103 ); + JSONTEST_ASSERT( root["false"].getOffsetLimit() == 108 ); + JSONTEST_ASSERT( root.getOffsetStart() == 0 ); + JSONTEST_ASSERT( root.getOffsetLimit() == 110 ); +} + + +JSONTEST_FIXTURE( ReaderTest, parseWithOneError ) +{ + Json::Reader reader; + Json::Value root; + bool ok = reader.parse( + "{ \"property\" :: \"value\" }", + root); + JSONTEST_ASSERT( !ok ); + JSONTEST_ASSERT( reader.getFormattedErrorMessages() == + "* Line 1, Column 15\n Syntax error: value, object or array expected.\n" ); + std::vector<Json::Reader::StructuredError> errors = + reader.getStructuredErrors(); + JSONTEST_ASSERT( errors.size() == 1 ); + JSONTEST_ASSERT( errors.at(0).offset_start == 14 ); + JSONTEST_ASSERT( errors.at(0).offset_limit == 15 ); + JSONTEST_ASSERT( errors.at(0).message == + "Syntax error: value, object or array expected." ); +} + + +JSONTEST_FIXTURE( ReaderTest, parseChineseWithOneError ) +{ + Json::Reader reader; + Json::Value root; + bool ok = reader.parse( + "{ \"pr佐藤erty\" :: \"value\" }", + root); + JSONTEST_ASSERT( !ok ); + JSONTEST_ASSERT( reader.getFormattedErrorMessages() == + "* Line 1, Column 19\n Syntax error: value, object or array expected.\n" ); + std::vector<Json::Reader::StructuredError> errors = + reader.getStructuredErrors(); + JSONTEST_ASSERT( errors.size() == 1 ); + JSONTEST_ASSERT( errors.at(0).offset_start == 18 ); + JSONTEST_ASSERT( errors.at(0).offset_limit == 19 ); + JSONTEST_ASSERT( errors.at(0).message == + "Syntax error: value, object or array expected." ); +} + + +JSONTEST_FIXTURE( ReaderTest, parseWithDetailError ) +{ + Json::Reader reader; + Json::Value root; + bool ok = reader.parse( + "{ \"property\" : \"v\\alue\" }", + root); + JSONTEST_ASSERT( !ok ); + JSONTEST_ASSERT( reader.getFormattedErrorMessages() == + "* Line 1, Column 16\n Bad escape sequence in string\nSee Line 1, Column 20 for detail.\n" ); + std::vector<Json::Reader::StructuredError> errors = + reader.getStructuredErrors(); + JSONTEST_ASSERT( errors.size() == 1 ); + JSONTEST_ASSERT( errors.at(0).offset_start == 15 ); + JSONTEST_ASSERT( errors.at(0).offset_limit == 23 ); + JSONTEST_ASSERT( errors.at(0).message == + "Bad escape sequence in string" ); +} + +  int main( int argc, const char *argv[] )  {  JsonTest::Runner runner; @@ -1512,6 +1641,14 @@  JSONTEST_REGISTER_FIXTURE( runner, ValueTest, compareObject );  JSONTEST_REGISTER_FIXTURE( runner, ValueTest, compareType );  JSONTEST_REGISTER_FIXTURE( runner, ValueTest, checkInteger ); + JSONTEST_REGISTER_FIXTURE( runner, ValueTest, offsetAccessors ); + + JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithNoErrors ); + JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithNoErrorsTestingOffsets ); + JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithOneError ); + JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseChineseWithOneError ); + JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithDetailError ); +  JSONTEST_REGISTER_FIXTURE( runner, WriterTest, dropNullPlaceholders );  return runner.runCommandLine( argc, argv );  }